
'''
    第4周作业：
通过Python编程完成一个银行ATM机模拟系统，具备如下功能:
(1)登陆验证：用户输入用户名密码登陆，检测用户名是否存在以及用户名密码是否匹配；用户名密码共有三次输入机会，超过三次系统退出。
(2)菜单界面：登陆成功后显示功能操作界面，输入序号选择对应功能。
(3)用户注册：用户可以输入用户名和密码创建自己的账号，并输入电话号码等信息，如果用户名存在则让用户重新输入用户名。注册后免费赠送5000元余额。
(4)账户管理：用户可以随时查看自己的账户余额。用户可以输入其他账户用户名，实现转账功能；转账时用户名必须存在，否则提示异常。用户也可以模拟实现存取款功能。
(5)用户名和密码以及账户信息等必须永久保存。且基于命令行完成，不需要开发GUI界面。
ps:实现方式不限 '''

'''构思：
        用户信息类(个体
            1：用户信息注册
                用户名重复检测，成功后赠送余额
                用户信息的永久保存，，使用序列化
        用户信息集（群体
        ATM操作类
            1：用户信息登录
                用户名存在性
                用户名及其密码匹配
                相同用户名三次输入错误退出系统
            2：功能操作界面
                对应序号选择对应功能
            3：具体功能
                查看用户余额
                转账，被转账用户名必须存在，否则提示异常，
                存款
                取款
        写完后可以改写增加一个操作类
        '''
import  string
import  json
import pickle
import msgpack
import  configparser
from pathlib import Path
#   配置文件初始化加载。
cfg = configparser.ConfigParser()
storage = Path('c:/test/test.ini')
storage.parent.mkdir(exist_ok=True)
storage.touch(exist_ok=True)
cfg.read(storage)

# 之前的密码判断
def t(m:str,length = 8):
    if len(m)<length:
        raise ValueError('抱歉，您的密码不够八位')
    elif m[0] not in set(string.ascii_letters+'_'):
        raise ValueError('抱歉，您的密码应以字母或下划线开头')
    table = [set(v) for v in [string.digits, string.ascii_uppercase,  string.ascii_lowercase, string.punctuation]]
    p = 0
    for i in range(4):
        if set(m)&table[i]:
            p+=1
    if p<3:
        raise ValueError('密码 simple')
    return p

class Users:    #信息汇总--账户
    def __init__(self):
        self.users = {}
        print('Users init')
    def __add__(self, user):
        print(user,user.name)
        if user.name:
            self.users[user.name] =user.__dict__
            print('{} 注册成功'.format(user.name))
            temp_name = user.name
            self.users[user.name].pop('name')
            '''保存在本地    c:/test/test.ini    路径中'''
            if cfg.has_section('name'):
                cfg['name'][temp_name] = temp_name
            else:
                cfg['name'] = {temp_name:temp_name}
            print(1,self.users)
            cfg[temp_name] = self.users[temp_name]
            with open(storage, 'w+', encoding='utf8') as f:
                cfg.write(f)
            # cfg.read(storage)#再读取一遍，防止ini和运行中程序不匹配？？？，会有这种异常吗？

            return self
        else:
            print('请先完成注册')
    def dumps(self,typ = 'json'):
        typ = typ.strip()
        if typ == 'json':
            return json.dumps(self.users)
        elif typ == 'pick':
            return pickle.dumps(self.users)
        else:
            return msgpack.dumps(self.users)
'''上为记录'''
class User:     #信息注册--账户
    def __init__(self,name:str,password:str,phone:str,kwa=''):
        # self.name = None    #避免
        try:
            if cfg.has_option('name',name):
                raise ValueError('reprt name')
            t(password)
        except ValueError as e:
            print(e)
        else:
            self.name = name
            self.password = password
            '''还可以有一个电话号码检测'''
            self.phone = phone
            self._balance = str(float(5000))    #带不带点的区别，使用float
            for i in kwa.split():
                i = i.split('=')
                self.__dict__[i[0]] = i[1]
'''注册时'''
class Atm:
    def __init__(self):
        '''作主程序'''
        self.func_b = '退出 登录 注册'.split()
        self.func_a = '退出 余额 存款 取款 转账'.split()
        self.users = Users()
        self.main_b()
    def menu(self,src ='b'):
        '''仅作为菜单界面'''
        print('欢迎使用****ATM自助银行\n使用某种功能请点击对应数字')
        if src == 'b':
            [print('{}\t{}'.format(i,self.func_b[i-1]))  for i in range(1,len(self.func_b)+1)]
        else:
            [print('{}\t{}'.format(i,self.func_a[i-1])) for i in range(1,len(self.func_a)+1)]
    def main_b(self):#atm 内操作
        '''登录前功能接口'''
        self.menu()
        inp = ''
        inp = input('请输入：').strip() if inp!='1' else '1'
        while inp!='1':
            if inp == '2':
                acc,paw,wrong,ac,pa = True,True,0,'',''
                '''输入账号，不存在--重新输入-根据输入继续判断或返回上一层
                              存在--输入密码--不正确--继续输入直到三次错误，强制退出
                                               正确--进行登入
                '''
                while (acc or paw) and ac!='quit':
                    ac = input('请输入账号（退出：quit）:').strip()
                    acc = False if cfg.has_option('name',ac) else True

                    '''当acc为False，paw为True 且 错误次数小于三次时执行以下代码'''

                    while paw and (wrong <3) and (not acc) and pa!='quit':
                        wrong+=1
                        pa = input('请输入密码（退出：quit）：').strip()
                        paw = False if pa == cfg[ac]['password'] else True
                        if paw == False:
                            self.menu('a')
                            self.main_a(ac)
                            '''直接进入'''
                    if wrong ==3:
                        break       #目前没有冻结账户的方法 ，可以利用ini文件中msection-name，中的option--名字，的值 value，设置为解绑时间，
            elif inp == '3':
                ac = AtmFunc.reg(self.users)
                if ac:
                    print('已为您自动登录\n正在为您跳转菜单界面')
                    self.menu('a')
                    self.main_a(ac)
            else:
                print('输入错误或输入非数字\n为您跳转至菜单栏') if inp !='1' else None
            self.menu()
            inp = input('请输入：').strip()
        with open(storage, 'w+', encoding='utf8') as f:
            cfg.write(f)
        print('已退出')
        return
    def main_a(self,ac):
        '''登录后功能接口'''
        account = AtmFunc(self.users, ac)
        inp = input('请输入：').strip()
        while inp!='1':
            if inp=='2':
                print(account.check())
            elif inp == '3':
                money = float(input('请输入存款金额：'))
                print(account.deposit(money))
            elif inp == '4':
                money = float(input('请输入取款金额：'))
                print(account.withdraw(money))
            elif inp =='5':
                gather = input('请输入收款人账号：').strip()
                money = float(input('请输入转账金额：'))
                print(account.trans2user(gather,money))
            else:
                print('输入错误\n为您跳转至菜单栏')
            self.menu('a')
            inp = input('请输入：').strip()
        self.main_b()

class AtmFunc:
    def __init__(self,users:Users,name):
        self.users = users
        self.name = name
        '''登录后'''
    @staticmethod
    def reg(users:Users):
        '''注册'''
        inp = ''
        tmp = []
        while not inp :
            for i in 'name password phone other'.split():
                tmp.append(' '.join(input('请输入{}：'.format(i)).split()))
            try:
                new_ac = User(tmp[0], tmp[1], tmp[2], tmp[-1])
            except ValueError as e :
                print('由于{} 注册不成功\n 请重新输入'.format(e))
                inp = input('如果退出注册请输入任意字符，继续请回车：')
            except Exception as e:
                print('由于{} 注册不成功\n 请重新输入'.format(e))
                inp = input('如果退出注册请输入任意字符，继续请回车：')
            else:
                users+new_ac
                return tmp[0]
    def check(self):
        '''查看余额'''
        return float(cfg[self.name]['_balance'])
    def deposit(self,money:float):
        '''存款'''
        tmp = float(cfg[self.name]['_balance'])+money
        cfg[self.name]['_balance']=str(tmp)
        return float(cfg[self.name]['_balance'])    #+=money
    def withdraw(self,money):
        '''取款'''
        tmp = float(cfg[self.name]['_balance'])-money
        if tmp >= 0 :
            cfg[self.name]['_balance']=str(tmp)
            return float(cfg[self.name]['_balance'])
        return '您的账户余额不足'
    def trans2user(self,account,money):
        '''转账'''
        if self.name==account:
            return '不能给自己转账'
        tmp = float(cfg[self.name]['_balance'])-money
        if tmp >= 0 :
            if cfg.has_section(account):
                cfg[self.name]['_balance'] = str(tmp)
                tmp = float(cfg[account]['_balance']) + money
                cfg[account]['_balance'] = str(tmp)
                return '{0:} 转账给 {1:} {2:} 元\n{0:} 的 余额为{3:}'.format(self.name,account,money,float(cfg[self.name]['_balance']))
            return '收款人不存在'
        return '您的账户余额不足'


if __name__ == '__main__':
    # jihe = Users()
    # a = User( 'a', 'woshimima#3_', '181')
    # jihe+a
    # print(jihe.__dict__['users'])
    # a = User(jihe, 'a', 'woshimima#3_', 181)
    # b = User(jihe, 'b', 'woshimima#3_', 181)
    # # c = User(jihe, 'a', 'w3_', '181')
    # jihe+a+b
    # print(jihe.__dict__['users']['a'])
    Atm()       #PS：测试不多，可能会有没有料到的问题
    # for k, v in cfg.items():
    #     for k,v in cfg.items(k):
    #         print(k,v)
'''回溯过程中不熟练的和占用过多时间的点：
    1.一开始分两个数据汇总类和数据处理类，到登录时不便于处理，改为了，注册信息类，和已注册集合类
    2.注册时，因为bool判断用了较多时间
    3.对类使用不熟练，一开始没有设计主程序的想法，只是想方法之间的互相调用
    4.关于数据保存，先是想到了序列化，但是感觉数据量一大会涉及反复文件IO，加上不是很熟练序列化的抽象想象，改用ini
    5.然后发现，眼睛会了ini，手不会，又自学了一遍ini，2333333333
    6.其次就是貌似已注册集合类都可以去掉了，直接调用ini，进行操作。但是并没有改
    
    总结：
    1.对类的设计有丁点进步
    2.复习了ini，path，序列化
        
    
    
    
    '''
# 有个问题：__add__ 这个魔术方法，一般来说有什么作用？

